2장. 객체를 생성하고 파괴하기

2.1 생성자 대신 static 팩토리 메소드를 고려한다.

2.1.1 장점

① static 팩토리 메소드는 알맞은 이름을 줄 수 있다.

  • 생성자에 전달되는 인자들만 보고 그 생성자가 리턴하는 객체의 특징을 알기 어렵다.
    그러나 static 팩토리 메소드는 이름을 통하여 자신이 리턴하는 객체의 특징을 설명할 수 있다.

//인자들만 객체의 특징을 알기 어렵다.
BigInteger bi = new BigInteger(5, new Random());

//메소드이름을 통하여 자신이 리턴하는 객체의 특징을 설명할 수 있다.
BigInteger bi = BigInteger.probablePrime(5, new Random());

  • 한 클래스에서 같은 시그니처를 갖는 생성자는 단 하나만 존재해야 한다.
    즉 같은 시그니처를 갖는 생성자가 여러 개 필요하다면, 알맞은 이름을 가진 static 팩토리 메소드로 바꾸는 것을 심각히 고려해야 한다.

② static 팩토리 메소드는 생성자와 달리 호출될 때마다 새로운 객체를 생성하지 않아도 된다.

  • WEBSTUDY:불변 클래스(Immutable class)와 같은 경우 스태틱 팩토리 메소드를 사용하면 미리 만들어 놓은 인스턴스를 계속 제공 할 수 있다.
  • 동일한 객체가 쓸데없이 생성되는 것을 막기 위해 생성된 인스턴스를 캐쉬(cach)에 저장해 놓고,
    필요할 때마나 캐쉬에 저장된 인스턴스를 꺼내서 제공 할 수 있다.

③ 스태틱 팩토리 메소드를 사용하면 리턴할 객체의 타입을 유연하게 선택 할 수 있다.

  • 스태틱 팩토리 메소드가 인터페이스 타입을 리턴하는 인터페이스 기반의 프레임워크에 들어 맞는다.
  • 스태틱 메소드를 사용하면 리턴받는 객체를 실제 구현 클래스 타입이 아닌 인터페이스 타입만 참조하도록 강제하는 장점도 있다.
  • 전달받은 인자에 따라 어떤 타입의 객체를 리턴할지 선택할 수 있다.
  • 리팩토링에서 나온 예제를 참고.

static Employee create(int type){
    switch(type){
      case ENGINEER :
        return create("Engineer");
      case SALESMAN :
        return create("Salesman");
      case MANAGER :
         return create("Manager");
      default :
        throw new IllegalArgumentException("Incorrect type code value");
    }
  }

  //Class.forName을 사용한다.
  static Employee create(String name){
    try{
      return (Employee)Class.forName(name).newInstance();
    }catch(Exception e)
      throw new IllegalArgumentException("Incorrect type code value");
    }
  }

  //client code..
  Employee emp = Employee.create("Engineer");

2.1.2 단점

  • 메소드를 정의한 클래스가 public 이나 protected 생성자를 제공하지 않으면, 다른 클래스가 이 클래스를 상속받을 수 없다.
  • 다른 스택틱 메소드와의 차이를 명시할 수 없다.

2.1.3 예제 코드

  • 교재 13 Page 소스 참고

참고1. 불변 클래스(Immutable class)

  • 대표적인 Immutable class로는 String 클래스가 있다.
  • Immutable class 설계는 내부 프라퍼티를 변경할수 있도록 하는 public 인터페이스를 구현하지 않으면 된다.
    즉, 객체의 어떤 메소드를 쓰더라도 한번 만들어진 내부 속성이 변하지 않게 하면 됩니다.

//SCJP 문제를 참고 했습니다. 어떤 값이 출력 될까요?
public static void main(String[] args) {

    String s = "Hello";
    String t = " " + "there";

    s.concat( t );
    s.toLowerCase();
    s += " my friend";

    System.out.println( s );
}

  • 정답은 : Hello my friend

2.2 private 생성자를 써서 싱글톤을 유지하라.

  • 싱글톤이란 정확히 하나의 인스턴스만 만들어지는 클래스이다.
  • 싱글톤은 스레드 풀이라던가, 캐시 등등 객체가 전체프로그램에서 오직 하나만 생성되어야 하는 경우에 사용한다.
  • 싱글톤은 두 가지 방법으로 구현 할 수 있다.
    두 방법 모두 생성자를 private로 정의하고 유일하게 인스턴스에 접근할 수 있는 public static 멤버를 제공한다.

2.2.1 public static final 멤버 필드 사용


public class Elvis{
  public static final Elvis INSTANCE = new Elvis();
  
  //생성자는 private로 정의 한다. 
  private Elvis(){  
  }
  
  //이하 생략
}

//Elvis.INSTANCE로 객체를 사용. 

2.2.2 스태틱 팩토리 메소드 사용


public class Elvis{
  private static final Elvis INSTANCE = new Elvis();
  
  private Elvis(){  
  }
  
  //스태틱 메소드 사용
  public static Elvis getInstance(){
    return INSTANCE;
  }
  
  //이하 생략
}

//Elvis.getInstance로 객체를 사용

2.3 private 생성자로 인스턴스를 만들지 못하게 하라.

  • static메소드와 static 필드로만 이루어진 클래스를 만들어야 할 때가 있다.
  • java.lang.Math나 java.util.Arrays나 아니면 직접 만든 유틸리티 클래스형태들.
  • 이런 클래스의 인스턴스는 아무런 의미가 없기 때문에 외부에서 인스턴스를 아예 만들지 못하게 하라.
    이유는 원래 의도와 다르게 기본 생성자를 사용해서 인스턴스를 생성해서 접근 할 수 있기 때문에
  • 인스턴스를 아예 만들지 못하게 하는 방법은? private 생성자를 만들어 주면 된다.

public class UtilityClass{

  //private생성자를 추가해서 컴파일러에서 자동으로 생성자를 추가하지 못하게 한다. 
  private UtilityClass(){
  
  }  
}

2.4 쓸데없는 객체를 중복 생성하지 마라

  • 동등한 기능을 하는 객체를 필요할 때마다 생성하는 것보다 한 객체를 재사용 하는 것이 낫다.
  • 불변 클래스(Immutable class)는 언제나 재사용 될 수 있다.

  //호출 될 때마다 새로운 인스턴스를 생성한다. 
  String s = new String("바보");  //오! 제발, 절대 이렇게 하지 말 것!
    
  //매번 새로운 인스턴스를 생성하지 않는다.   
  String s = "이젠 바보 아니야";  //이렇게 사용해라  

  • 생성자와 스태틱 팩토리 메소드를 모두 제공하는 불변 클래스가 있을 때
  • 스태틱 메소드를 사용하는 것이 낫다. 스태틱 팩토리 메소드는 새로운 객체를 생성하지 않아도 된다.
  • Boolean(String)보다는 Boolean.valueOf(String)을 쓰는것이 낫다.
개선 전개선 후
{code}
public class Person{
private final Data birthDate;

public Person(Date birthDate){
this.birthDate = birthDate;
}

public boolean isBabyBoomer(){
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();

gmtCal.set(1956, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();

return birthDate.compareTo(BOOM_START >= 0 &&
birthDate.compareTo(BOOM_END) < 0;
}
}

|

class Person{

private final Data birthDate;
public Person(Date birthDate){
this.birthDate = birthDate;
}

//베이비 붐의 시작 및 종료 일자
private static final Date BOOM_START;
private static final Date BOOM_END;

static{
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();

gmtCal.set(1956, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}

public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START >= 0 &&
birthDate.compareTo(BOOM_END) < 0;
}
}

|

h2. 2.5 쓸모없는 객체 참조는 제거하라
 * 메모리 누수 위험을 사전에 방지해야 한다. 
 * 자바에서 메모리 누수란 가비지컬렉션 되어야 하는데도 그 대상이 되지 못하는 객체들이 생기는 경우를 말한다. 
 * 가비지 컬렉터가 있는 언어에서 메모리 누수 현상은 매우 위험하다. 
||개선 전||개선 후||
|

// 어디서 "메모리가 새는지(memory leak)" 찾을 수 있겠는가?
public class Stack{
private object[] elements;
private int size=0;

public Stact(int initialCapacity){
tiis.elements = new Object[initialCapacity];
}

public volid push(Object e){
ensureCapacity();
elements[size++];
}

public Ojbect pop(){
if(size == 0){
throw new EmptyStackException();
return elements[--size];
}

/* 최소한 하나의 구성요소를 담을 수 있는 여유를 두어야 하며

  • 여유가 없을 때 구성요소를 담는 배열의 크기를 두배로 늘린다.
    */
    private void ensureCapacity(){
    if(elements.length == size){
    Object[] oldElements = elements;
    elements = new Obejct[2*element.length+1];
    System.arraycopy(oldElements, 0, elements, 0, size);
    }
    }
    }
|

public Object pop(){
if(size == 0){
throw new EmptyStackException();

Object result = elements[--size];
element[size] = null; // 쓸모 없는 참조를 없앤다.

return result;
}

|

 * Stack클래스의 elements배열과 같이 임시로 다른 객체에 대한 참조를 보관하는 멤버 필드가 있으면 메모리 누수에 대해 항상 생각해야 한다. 

h3. 참고2. WeakHashMap
 * WeakHashMap 은 HashMap 과 거의 동일하다.
 * 다른점은  Map 에서 key 로 접근할 수 없는 Entry 는 자동으로 삭제 한다.
 * 즉 시간이 지나면 Garbage Collector 가 WeakHashMap 에서 null 값인 키와 그에 해당하는 값을 제거한다.
 * 하지만 HashTable 은 그렇지 않고 그 값을 그대로 유지한다.

h3. 참고3. 가비지 컬렉션
* 자바의 모든 배열과 객체들은 힙(heap) 이라는 메모리 공간에 저장된다.
* new 키워드를 쓸 때마다 힙에 새로운 메모리가 객체마다 활당된다. 
* 자바는 C++과 같이 메모리를 반환하는 방법이 없고, 가비지 컬렉터(Garbage Colle:GC)가 사용하지 않는 메모리를 자동으로 수거하는 기능을 한다.
* 참고 URL
** http://j2eestudy.co.kr/lecture/lecture_read.jsp?db=lecture0401_1&table=j2ee&id=1
** http://standcode.com.ne.kr/java/gc/Garbage.ppt
** http://blog.naver.com/dorabn?Redirect=Log&logNo=40047675209

h2. 2.6 종료자(finalizer)들을 쓰지 마라

h4. 2.6.1 종료자란?
 * Object 클래스에는 protected 메소드인 finalize 메소드가 있는데 이 객체는 자바의 모든 클래스에서 재정의 할 수 있다.
 * 특정 객체에서 실제로 호출되는 finalize 메소드를 이 객체의 종료자라 한다 

h4. 2.6.2 finalize의 실행 시점
 * 가비지콜렉터가 실행 될 때 자원을 해제하기 전에 finalize 메소드를 호출 한다.  즉 인스턴스가 사라지기 전에 마지막으로 실행된다.  
 * finalize 메소드는 GC가 해당 인스턴스를 가비지로 판단해서 삭제하기 전 실행되기 때문에.
   특정한 인스턴스에 참조가 없다고 하더라도, 언제 GC에 수집될지 전혀 모른다.  
   즉 finalize메소드에 일반적인 코드가 있으면 언제 실행될지 모른다. 


h4. 2.6.3 종료자의 올바른 사용법
 * 객체가 소멸되기전에 다른자원도 정리해야 한다면, finalize 메소드를 재정의해 사용할 수 있다.
   즉 명시적인 종결처리 메소드를 호출하는 것을 잊었을 경우에 대비해 안전망 역할을 할 수 있다.
   하지만 이것도한 종료자가 바로 호출되거나, 반드시 호출 된다는 보장은 없다.


//종료자는 직접 상위 클래스의 종료자를 호출해야 한다.
protected void finalize() throws Throwable {
try {
//하위 클래스 종료 처리
close();
} finally {
super.finalize(); //상위 클래스 종료자 호출
}
}




\\
\\

h2. 문서에 대하여

* 이 문서의 내용은 [자바 유창하게 말하기 - Effective Java|http://book.naver.com/bookdb/book_detail.php?bid=130814] 교재를 스터디 하면서 정리한 내용 입니다.
* 최초작성자 : [김정식]
* 최초작성일 : 2008년 2월 18일
* 이 문서는 [오라클클럽|http://www.gurubee.net] [자바 웹개발자 스터디|제3차 자바 웹개발자 스터디] 모임에서 작성하였습니다.
* 이 문서를 다른 블로그나 홈페이지에 퍼가실 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^\^